#include <iostream>
#include <fcntl.h>
#include <stdlib.h>
#include <list>
#include <sys/wait.h>
#include <vector>
#include "Caja.h"
#include "ProductorDeClientes.h"
#include "AsignadorDeCaja.h"
#include "JefeDeEstacion.h"
#include "logger/Logger.h"
#include "Empleado.h"
#include "Administrador.h"
#include "Constantes.h"
#include <cstring>
#include "msgQueue/Cola.h"
#include "MensajePeticionCaja.h"
using namespace std;

void monitorear_consola_para_salir() {
	string consoleBuffer = "";
	while (consoleBuffer != "q") {
		cout << "Ingresar q para terminar la simulacion:";
		cin >> consoleBuffer;
	}
}

void detener_todos_los_procesos(const ProductorDeClientes& productor,
		const JefeDeEstacion& jefe, const Administrador& administrador,
		vector<Empleado>& empleados, Logger& logger) {

	// Mando senial para que termine cada empleado
	vector<Empleado>::iterator it = empleados.begin();
	while (it != empleados.end()) {
		stringstream ss;
		ss << it->getId()+1;
		logger.info("Se enviara senial para detener al empleado: " + ss.str());
		it->stop();
		waitpid(it->getPID(), NULL, 0);
		++it;
	}

	// Mando senial para que termine el productor de clientes
	logger.info("Se enviara senial para detener el productor de clientes.");
	productor.stop();

	// Mando senial para que terminen el jefe
	logger.info("Se enviara senial para detener el jefe de estacion.");
	jefe.stop();

	// Mando senial para que termine el administrador
	logger.info("Se enviara senial para detener al administrador.");
	administrador.stop();


	waitpid(jefe.getPID(), NULL, 0);
	waitpid(productor.getPID(), NULL, 0);
	waitpid(administrador.getPID(), NULL, 0);
}

void liberar_recursos_compartidos(
		Caja& caja,
		Semaforo& semaforoAccesoCaja,
		vector<Surtidor>& surtidores,
		vector<Semaforo>& semaforos,
		Administrador& administrador,
		Cola<MensajePeticionCaja>& colaPeticionCaja) {
	caja.liberar();
	semaforoAccesoCaja.eliminar();
	unsigned int size_surtidores = surtidores.size();
	//elimino de forma independiente ya que quizas
	for (unsigned int i = 0; i < size_surtidores; ++i)
	{
		surtidores[i].liberar();
		semaforos[i].eliminar();
	}
	administrador.eliminar();
	colaPeticionCaja.destruir();
}
/* Destruye archivos de memoria compartida */
void destruir_surtidores(const unsigned int cant_de_surtidores)
{
	for (unsigned int i = 0; i < cant_de_surtidores; ++i)
	{
		stringstream ss;
		ss << (i+1);
		string file_name = Constantes::ARCHIVO_PREFIJO_SURTIDOR + ss.str();
		remove(file_name.c_str());
	}
}

bool obtener_valores_entrada(
		char** argv,
		unsigned int& cant_de_surtidores,
		unsigned int& cant_empleados,
		bool& is_modo_debug)
{
	std::string surtidores(argv[1]);
	std::string empleados(argv[2]);
	std::string modo_debug(argv[3]);
	bool entrada_erronea = false;
	for (unsigned int i = 0; i< surtidores.size(); i++)
	{
		if (not isdigit(surtidores[i]))
		{
			entrada_erronea = true;
			break;
		}
	}
	for (unsigned int i = 0; i< empleados.size(); i++)
	{
		if (not isdigit(empleados[i]))
		{
			entrada_erronea = true;
			break;
		}
	}
	if(modo_debug.size() > 1)
	{
		entrada_erronea = true;
	}
	else
	{
		if(not isdigit(modo_debug[0]))
		{
			entrada_erronea = true;
		}
	}
	if(not entrada_erronea)
	{

		cant_empleados = atoi(empleados.c_str());
		is_modo_debug = (atoi(modo_debug.c_str()) != 0);
		cant_de_surtidores = atoi(surtidores.c_str());
	}
	return not entrada_erronea;
}


int main(int argc, char** argv) {

	if (argc == (Constantes::CANT_PARAMETROS + 1)) { // + 1 por el nombre del programa

		unsigned int numero_de_empleados = 2;
		unsigned int cant_de_surtidores = 2;
		bool modo_debug = true;
		if (obtener_valores_entrada(argv, cant_de_surtidores, numero_de_empleados,
				modo_debug)) {

			pid_t productor_PID, jefe_PID, empleado_PID, administrador_PID,asignador_PID;
			ProductorDeClientes productor("ProdClie", modo_debug);
			Administrador administrador("Admin", modo_debug);
			vector<Empleado> empleados; // Lista con los procesos de empleados

			// Recursos compartidos
			Caja caja;
			Semaforo semaforo_acceso_caja(Constantes::ARCHIVO_SEMAFORO_CAJA, 1,1);
			Cola<MensajePeticionCaja> colaMensajesCaja(Constantes::ARCHIVO_COLA_MENSAJES_CAJA,'a');
			// El fifo que usa el prod-consumidor lo liberan ellos

			Logger logger(Constantes::ARCHIVO_LOG, "Main", true);
			logger.info("Inicio de la simulacion.");

			caja.inicializar();
			logger.info("Se inicializo la caja con monto = 0");

			// CREACION DE SURTIDORES Y SUS SEMAFOROS
			vector<Surtidor> surtidores;
			vector<Semaforo> semaforos;
			logger.info("Creando surtidores y semaforos");
			for (unsigned int i = 0; i < cant_de_surtidores; i++) {
				stringstream ss;
				ss << (i + 1);
				string file_name = Constantes::ARCHIVO_PREFIJO_SURTIDOR
						+ ss.str();
				int fd = open(file_name.c_str(), O_RDWR | O_CREAT, 0644); //crea archivo para recurso compartido
				if (fd != -1) {
					close(fd);
					Surtidor surtidor(file_name);
					surtidores.push_back(surtidor);
					Semaforo semaforo(file_name, 1, 1);
					semaforos.push_back(semaforo);
					string msg = "Creado surtidor y semaforo " + ss.str();
					logger.info(msg);
				} else {
					//Se informa el error pero sigue probando con los demas surtidores
					logger.warn("No se pudo crear surtidor " + ss.str());
				}
			}

			// Se lanza el proceso del productor de clientes
			productor_PID = fork();
			if (productor_PID == 0) {
				// Proceso hijo productor
				productor.run();
				return 0;
			}
			// Setear el pid al objeto que representa al proceso, que tiene el main
			productor.setPID(productor_PID);
			logger.info("Se inicio el productor de clientes.");

			// Setear el pid al objeto que representa al proceso, que tiene el main
			logger.info("Se inicio el jefe de estacion.");

			// Se lanza el proceso del administrador
			administrador_PID = fork();
			if (administrador_PID == 0) {
				// Proceso hijo administrador
				administrador.run();
				return 0;
			}
			// Setear el pid al objeto que representa al proceso, que tiene el main
			administrador.setPID(administrador_PID);
			logger.info("Se inicio el administrador.");
			JefeDeEstacion jefe("JefeEst", modo_debug, numero_de_empleados);

			// Se lanzan los procesos de cada empleado
			for (unsigned int i = 0; i < numero_de_empleados; i++) {
				stringstream ss;
				ss << (i + 1);
				// El nombre del proceso del empleado es empleado mas un numero
				string nombre_proceso_empleado = string("Empleado") + ss.str();
				Empleado empleado(i, nombre_proceso_empleado, surtidores,semaforos, modo_debug, i, numero_de_empleados);

				// Se lanza el proceso del empleado
				empleado_PID = fork();
				if (empleado_PID == 0) {
					// Proceso hijo empleado
					empleado.run();
					return 0;
				}
				jefe.setIdEmpleado(i, empleado_PID);
				empleado.setPID(empleado_PID);
				jefe.addEmpleado(&empleado);

				// Setear el pid al objeto que representa al proceso, que tiene el main
				empleado.setPID(empleado_PID);

				// Se guarda el objeto que representa al proceso en la lista que tiene main
				empleados.push_back(empleado);
				logger.info("Se creo el empleado: " + ss.str());
			}
			// Se lanza el proceso del jefe de estacion
			jefe_PID = fork();
			jefe.setPID(jefe_PID);

			if (jefe_PID == 0) {
				jefe.run();
				return 0;
			}

			AsignadorDeCaja asignador(empleados,administrador,modo_debug);
			asignador_PID = fork();
			if (asignador_PID == 0)
			{
				asignador.run();
				return 0;
			}
			// Monitorear la consola hasta que ingresen q para detener la simulacion
			monitorear_consola_para_salir();

			// Se presiono q, se detienen y esperan la termiancion de todos los proc hijos
			detener_todos_los_procesos(productor, jefe, administrador,
					empleados, logger);

			// Liberar los recursos (memorias compartidas)
			logger.info("Liberando recursos compartidos");
			liberar_recursos_compartidos(caja, semaforo_acceso_caja, surtidores,
					semaforos, administrador, colaMensajesCaja);
			logger.info("Destruyendo surtidores");
			destruir_surtidores(cant_de_surtidores);
			jefe.eliminar();
			logger.info("Fin de la simulacion.");
		}
		else
		{
			cerr << "Errores en valores de entrada";
		}
	}
	else
	{
		cerr << "Error en cantidad de parametros de entrada";
	}
	return 0;
}
